#include "ImGuiApp.h"
#include "implot.h"
#include "implot_internal.h"
#include "GNUGetText.h"
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <map>
#include <array>

const ImGuiTableFlags MY_TABLE_FLAGS = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV | ImGuiTableFlags_SizingFixedFit;
const ImGuiTableFlags MY_TABLE_FLAGS_SCROLL_XY = ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | MY_TABLE_FLAGS;

const double EPS = 1.0E-12;
const double EPS_LARGE = 1.0E-6;
const double CAPTURE_RADIANCE_PROB_2 = 6.0 / 11.0;

GNUGetText gt;

inline std::string _S(const std::string& s) {
	return gt.GetText(s);
}

inline std::string _IGS(const std::string& s) {
	return gt.GetText(s) + "###" + s;
}

#define _(s) (_S((s)).c_str())
#define _IG(s) (_IGS((s)).c_str())

// Helper to display a little (?) mark which shows a tooltip when hovered.
// In your own code you may want to display an actual icon if you are using a merged icon fonts (see docs/FONTS.md)
static void MyHelpMarker(const char* desc) {
	ImGui::TextDisabled("(?)");
	if (ImGui::BeginItemTooltip()) {
		ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
		ImGui::TextUnformatted(desc);
		ImGui::PopTextWrapPos();
		ImGui::EndTooltip();
	}
}

class MyDrawHighlight {
private:
	int x;
	std::vector<std::pair<ImU32, std::string> > tooltips;
public:
	MyDrawHighlight() : x(0x80000000) {
		if (ImPlot::IsPlotHovered()) {
			ImPlotPoint mouse = ImPlot::GetPlotMousePos();
			mouse.x = std::round(mouse.x);
			float tool_l = ImPlot::PlotToPixels(mouse.x - 0.5, mouse.y).x;
			float tool_r = ImPlot::PlotToPixels(mouse.x + 0.5, mouse.y).x;
			float tool_t = ImPlot::GetPlotPos().y;
			float tool_b = tool_t + ImPlot::GetPlotSize().y;
			ImPlot::PushPlotClipRect();
			ImPlot::GetPlotDrawList()->AddRectFilled(ImVec2(tool_l, tool_t), ImVec2(tool_r, tool_b), IM_COL32(128, 128, 128, 64));
			ImPlot::PopPlotClipRect();
			x = (int)mouse.x;
		}
	}
	~MyDrawHighlight() {
		if (!tooltips.empty()) {
			if (ImGui::BeginTooltip()) {
				for (const auto& tooltip : tooltips) {
					ImPlot::ItemIcon(tooltip.first);
					ImGui::SameLine();
					ImGui::TextUnformatted(tooltip.second.c_str());
				}
				ImGui::EndTooltip();
			}
		}
	}
	template <class T, class F>
	void PlotLine(const char* label_id, const T* values, int count, F& f, int xstart = 0, ImPlotLineFlags flags = 0) {
		ImPlot::PlotLine(label_id, values, count, 1.0, (double)xstart, flags);
		auto item = ImPlot::GetCurrentContext()->PreviousItem;
		if (item != NULL && item->Show) {
			if (x - xstart >= 0 && x - xstart < count) {
				tooltips.emplace_back(std::pair<ImU32, std::string>(item->Color, f(x, values[x - xstart])));
			}
		}
	}
	template <class T, class F>
	void PlotLine(const char* label_id, const T* values, const T& min, int count, F& f, int xstart = 0, ImPlotLineFlags flags = 0) {
		if (count > 2) {
			int i = count - 1;
			for (; i > 0; i--) {
				if (values[i] >= min) break;
			}
			count = std::min(i + 2, count);
			int m = count - 1;
			for (i = 0; i < m; i++) {
				if (values[i] >= min) break;
			}
			i--;
			if (i > 0) {
				values += i;
				count -= i;
				xstart += i;
			}
		}
		PlotLine(label_id, values, count, f, xstart, flags);
	}
	template <class T, class F>
	void PlotLine(const char* label_id, const T* values, const T& min, const T& max, int count, F& f, int xstart = 0, ImPlotLineFlags flags = 0) {
		if (count > 2) {
			int i = count - 1;
			for (; i > 0; i--) {
				if (values[i] >= min && values[i] <= max) break;
			}
			count = std::min(i + 2, count);
			int m = count - 1;
			for (i = 0; i < m; i++) {
				if (values[i] >= min && values[i] <= max) break;
			}
			i--;
			if (i > 0) {
				values += i;
				count -= i;
				xstart += i;
			}
		}
		PlotLine(label_id, values, count, f, xstart, flags);
	}
};

class MyFormatProb {
private:
	std::string f;
public:
	MyFormatProb(const std::string& f) : f(f) {}
	std::string operator()(int x, double y) const {
		char buf[1024];
		snprintf(buf, sizeof(buf), f.c_str(), x, y * 100.0);
		return buf;
	}
};

enum WishType {
	CHARACTER_EVENT_WISH,
	WEAPON_EVENT_WISH,
	CHRONICLED_WISH,
	STANDARD_WISH,
};

const char* wishTypeText[] = {
	"Character Event Wish",
	"Weapon Event Wish",
	"Chronicled Wish",
	"Standard Wish",
};

double calcOneshotProb(int wt, int pc) {
	if (wt == WEAPON_EVENT_WISH) {
		return pc < 74 ? (0.007 + std::max(0.0, (pc - 62) * 0.07)) : std::min(1.0, 0.812 + std::max(0.0, (pc - 74) * 0.035));
	}
	else {
		return std::min(1.0, 0.006 + std::max(0.0, (pc - 73) * 0.06));
	}
}

void calcPercentile(const std::vector<double>& input, std::vector<int>& output, int offset) {
	output.resize(101);
	int p = 0, i = 0, m = (int)input.size();
	for (; i < m; i++) {
		for (; p * 0.01 <= input[i] && p <= 100; p++) {
			output[p] = i + offset;
		}
	}
	for (; p <= 100; p++) {
		output[p] = m - 1 + offset;
	}
}

struct Next5StarProb {
	int lastWishType, lastPityCounter;
	// probability of wish `n` is 5-star conditional on no 5-star before
	std::vector<double> oneshotProb;
	// probability of wish `n` is 5-star and no 5-star before
	std::vector<double> exactProb;
	// probability of exists 5-star in first `n` wishes
	std::vector<double> yesProb;
	std::vector<int> percentile;

	Next5StarProb() : lastWishType(-1), lastPityCounter(-1) {}

	void calc(int wt, int pc) {
		wt = std::min(std::max(wt, 0), IM_ARRAYSIZE(wishTypeText) - 1);
		pc = std::min(std::max(pc, 0), 100);

		if (wt == lastWishType && pc == lastPityCounter && !oneshotProb.empty()) return;
		lastWishType = wt;
		lastPityCounter = pc;

		oneshotProb.clear();
		exactProb.clear();
		yesProb.clear();

		oneshotProb.push_back(calcOneshotProb(wt, pc));
		exactProb.push_back(0.0);
		yesProb.push_back(0.0);

		double last = 1.0;
		while (last >= EPS) {
			pc++;
			double p = calcOneshotProb(wt, pc);
			double e = last * p;
			oneshotProb.push_back(p);
			exactProb.push_back(e);
			last -= e;
			yesProb.push_back(1.0 - last);
		}

		calcPercentile(yesProb, percentile, lastPityCounter);
	}
	void drawChart() const {
		if (oneshotProb.empty()) return;
		if (ImPlot::BeginPlot("Prob. of next 5-star item")) {
			ImPlot::SetupAxes("n", "prob.", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
			{
				MyDrawHighlight hl;
				hl.PlotLine("cond. prob. of 5-star at wish 'n'", oneshotProb.data(), (int)oneshotProb.size(),
					MyFormatProb("cond. prob. of 5-star at wish %d: %0.4f%%"), lastPityCounter);
				hl.PlotLine("prob. of 5-star at wish 'n'", exactProb.data(), EPS_LARGE, (int)exactProb.size(),
					MyFormatProb("prob. of 5-star at wish %d: %0.4f%%"), lastPityCounter);
				hl.PlotLine("prob. of 5-star at first 'n' wishes", yesProb.data(), EPS_LARGE, 1.0 - EPS_LARGE, (int)yesProb.size(),
					MyFormatProb("prob. of 5-star at first %d wishes: %0.4f%%"), lastPityCounter);
			}
			ImPlot::EndPlot();
		}
	}
	void drawTable() const {
		if (oneshotProb.empty()) return;
		if (ImGui::BeginTable("Table", 4, MY_TABLE_FLAGS_SCROLL_XY, ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * 10))) {
			ImGui::TableSetupScrollFreeze(1, 1);

			ImGui::TableSetupColumn("n", ImGuiTableColumnFlags_NoHide);
			ImGui::TableSetupColumn("cond. prob.");
			ImGui::TableSetupColumn("prob. of 5-star at 'n'");
			ImGui::TableSetupColumn("prob. of 5-star at or before 'n'");
			ImGui::TableHeadersRow();

			ImGuiListClipper clipper;
			clipper.Begin((int)oneshotProb.size());
			while (clipper.Step()) {
				for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
					ImGui::TableNextRow();
					ImGui::TableSetColumnIndex(0);
					ImGui::Text("%d", lastPityCounter + row);
					ImGui::TableSetColumnIndex(1);
					ImGui::Text("%0.4f%%", oneshotProb[row] * 100.0);
					ImGui::TableSetColumnIndex(2);
					ImGui::Text("%0.4f%%", exactProb[row] * 100.0);
					ImGui::TableSetColumnIndex(3);
					ImGui::Text("%0.4f%%", yesProb[row] * 100.0);
				}
			}

			ImGui::EndTable();
		}
	}
	void drawPercentile() const {
		if (percentile.size() < 101) return;
		if (ImGui::BeginTable("Percentile", 2, MY_TABLE_FLAGS)) {
			ImGui::TableSetupColumn("p", ImGuiTableColumnFlags_NoHide);
			ImGui::TableSetupColumn("percentile");
			ImGui::TableHeadersRow();

			const int p[] = { 1, 5, 10, 25, 50, 75, 90, 95, 99 };

			for (int i = 0; i < IM_ARRAYSIZE(p); i++) {
				ImGui::TableNextRow();
				ImGui::TableSetColumnIndex(0);
				ImGui::Text("%d%%", p[i]);
				ImGui::TableSetColumnIndex(1);
				ImGui::Text("%d", percentile[p[i]]);
			}

			ImGui::EndTable();
		}
	}
};

template <class T>
struct VectorWithOffset {
	int offset;
	std::vector<T> data;

	VectorWithOffset() : offset(0x7FFFFFFF) {}

	void shrink(const T& min) {
		if (data.size() <= 2) return;
		int i = (int)data.size() - 1;
		for (; i > 0; i--) {
			if (data[i] >= min) break;
		}
		if (i + 2 < (int)data.size()) {
			data.erase(data.begin() + (i + 2), data.end());
		}
		int m = (int)data.size() - 1;
		for (i = 0; i < m; i++) {
			if (data[i] >= min) break;
		}
		i--;
		if (i > 0) {
			data.erase(data.begin(), data.begin() + i);
			offset += i;
		}
	}

	void shrink(const T& min, const T& max) {
		if (data.size() <= 2) return;
		int i = (int)data.size() - 1;
		for (; i > 0; i--) {
			if (data[i] <= max) break;
		}
		data.erase(data.begin() + (i + 2), data.end());
		int m = (int)data.size() - 1;
		for (i = 0; i < m; i++) {
			if (data[i] >= min) break;
		}
		i--;
		if (i > 0) {
			data.erase(data.begin(), data.begin() + i);
			offset += i;
		}
	}

	void add(int i, const T& value) {
		if (data.empty() && i < offset) offset = i;
		assert(i >= offset);
		if (i >= offset) {
			if (i - offset >= (int)data.size()) {
				data.resize(i - offset + 1, T(0));
			}
			data[i - offset] += value;
		}
	}

	void add(int i, const T& value, const T& min) {
		if (value >= min) add(i, value);
	}

	template <class F>
	void map(F& f, VectorWithOffset<T>& output) const {
		for (int i = 0; i < (int)data.size(); i++) {
			int c = f(offset + i);
			output.add(c, data[i]);
		}
	}

	void cumsum(bool reverse = false) {
		if (data.size() <= 1) return;
		if (reverse) {
			T value = data.back();
			for (int i = (int)data.size() - 2; i >= 0; i--) {
				value += data[i];
				data[i] = value;
			}
		} else {
			T value = data[0];
			for (int i = 1; i < (int)data.size(); i++) {
				value += data[i];
				data[i] = value;
			}
		}
	}

	void toOutcomesByCount(int n, std::vector<VectorWithOffset<T> >& outcomesByCount) const {
		for (int i = 0; i < (int)data.size(); i++) {
			int j = i + offset;
			while (j >= (int)outcomesByCount.size()) {
				outcomesByCount.push_back(VectorWithOffset<T>());
			}
			if (j >= 0) {
				VectorWithOffset<T>& o = outcomesByCount[j];
				o.add(n, data[i]);
			}
		}
	}
};

struct Outcome5Star {
	int lastWishType, lastPityCounter, max5Star;
	VectorWithOffset<double> lastState;
	std::vector<VectorWithOffset<double> > outcomes, outcomesByCount;

	struct StateMap {
		int operator()(int i) const {
			return i >> 7;
		}
	};

	Outcome5Star() : lastWishType(-1), lastPityCounter(-1), max5Star(0) {}
	void calc(int m, int wt = -1, int pc = -1) {
		if (wt < 0) wt = lastWishType;
		if (pc < 0) pc = lastPityCounter;

		wt = std::min(std::max(wt, 0), IM_ARRAYSIZE(wishTypeText) - 1);
		pc = std::min(std::max(pc, 0), 100);

		if (wt != lastWishType || pc != lastPityCounter) {
			lastWishType = wt;
			lastPityCounter = pc;
			max5Star = 0;

			outcomes.clear();
			outcomesByCount.clear();

			lastState.offset = pc;
			lastState.data.clear();
			lastState.data.push_back(1.0);

			VectorWithOffset<double> o;
			o.data.push_back(1.0);
			o.offset = 0;
			outcomes.push_back(o);
			outcomesByCount.push_back(o);
		}

		while (m + 1 > (int)outcomes.size()) {
			VectorWithOffset<double> newState, newOutcome;

			newState.offset = lastState.offset + 1;
			for (int i = 0; i < (int)lastState.data.size(); i++) {
				int st = lastState.offset + i;
				int statePC = st & 0x7F;
				int state5StarCount = st >> 7;
				double q = lastState.data[i];
				double p = calcOneshotProb(wt, statePC + 1);
				newState.add((state5StarCount + 1) << 7, q * p, EPS);
				newState.add(st + 1, q * (1.0 - p), EPS);
			}
			newState.shrink(EPS);
			lastState = newState;

			newOutcome.offset = 0;
			newState.map(StateMap(), newOutcome);
			newOutcome.shrink(EPS);
			max5Star = std::max(max5Star, newOutcome.offset + (int)newOutcome.data.size() - 1);
			outcomes.push_back(newOutcome);
			newOutcome.cumsum(true);
			newOutcome.toOutcomesByCount((int)outcomes.size() - 1, outcomesByCount);
		}
	}
	static void drawOutcomeLine(const std::vector<VectorWithOffset<double> >& outcomes, MyDrawHighlight& hl, int m, const std::string& mStr, bool cumsum = false, const char* itemName = NULL) {
		if (m >= 0 && m < (int)outcomes.size()) {
			std::string itemNameTmp;
			if (itemName == NULL) {
				itemNameTmp = _S("5-star");
				itemName = itemNameTmp.c_str();
			}
			VectorWithOffset<double> o = outcomes[m];
			std::string s = _S("prob. of get %d %1 after %2 wishes");
			size_t lps = s.find("%1");
			if (lps != std::string::npos) {
				s = s.substr(0, lps) + itemName + s.substr(lps + 2);
			}
			std::string title = s;
			lps = title.find("%2");
			if (lps != std::string::npos) {
				title = title.substr(0, lps) + mStr + title.substr(lps + 2);
			}
			lps = title.find("%d");
			if (lps != std::string::npos) {
				title = title.substr(0, lps) + "'n'" + title.substr(lps + 2);
			}
			lps = s.find("%2");
			if (lps != std::string::npos) {
				s = s.substr(0, lps) + std::to_string(m) + s.substr(lps + 2);
			}
			hl.PlotLine(title.c_str(), o.data.data(), EPS_LARGE, (int)o.data.size(), MyFormatProb(s + ": %0.4f%%"), o.offset);
			if (cumsum) {
				o.cumsum(true);
				s = _S("prob. of get at least %d %1 after %2 wishes");
				lps = s.find("%1");
				if (lps != std::string::npos) {
					s = s.substr(0, lps) + itemName + s.substr(lps + 2);
				}
				title = s;
				lps = title.find("%2");
				if (lps != std::string::npos) {
					title = title.substr(0, lps) + mStr + title.substr(lps + 2);
				}
				lps = title.find("%d");
				if (lps != std::string::npos) {
					title = title.substr(0, lps) + "'n'" + title.substr(lps + 2);
				}
				lps = s.find("%2");
				if (lps != std::string::npos) {
					s = s.substr(0, lps) + std::to_string(m) + s.substr(lps + 2);
				}
				hl.PlotLine(title.c_str(), o.data.data(), EPS_LARGE, 1.0 - EPS_LARGE, (int)o.data.size(), MyFormatProb(s + ": %0.4f%%"), o.offset);
			}
		}
	}
	void drawOutcomeChart10() const {
		if (ImPlot::BeginPlot("Prob. of 5-star after 1 and 10 wishes")) {
			ImPlot::SetupAxes("num. of 5-star items", "prob.", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
			{
				MyDrawHighlight hl;
				drawOutcomeLine(outcomes, hl, 1, "1");
				drawOutcomeLine(outcomes, hl, 10, "10");
			}
			ImPlot::EndPlot();
		}
	}
	static void drawOutcomeChart(const std::vector<VectorWithOffset<double> >& outcomes, int m, const char* itemName = NULL) {
		std::string itemNameTmp;
		if (itemName == NULL) {
			itemNameTmp = _S("5-star");
			itemName = itemNameTmp.c_str();
		}
		std::string s = _S("Prob. of %1 after 'm' wishes");
		size_t lps = s.find("%1");
		if (lps != std::string::npos) {
			s = s.substr(0, lps) + itemName + s.substr(lps + 2);
		}
		if (ImPlot::BeginPlot(s.c_str())) {
			s = _S("num. of %1");
			lps = s.find("%1");
			if (lps != std::string::npos) {
				s = s.substr(0, lps) + itemName + s.substr(lps + 2);
			}
			ImPlot::SetupAxes(s.c_str(), _("prob."), ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
			{
				MyDrawHighlight hl;
				drawOutcomeLine(outcomes, hl, m, "'m'", true, itemName);
			}
			ImPlot::EndPlot();
		}
	}
	void drawOutcomeChart(int m) const {
		drawOutcomeChart(outcomes, m);
	}
	static void drawNumWishesChart(const std::vector<VectorWithOffset<double> >& outcomesByCount, int m, const char* itemName = NULL) {
		std::string itemNameTmp;
		if (itemName == NULL) {
			itemNameTmp = _S("5-star");
			itemName = itemNameTmp.c_str();
		}
		std::string s = _S("prob. of get at least %d %1 after %2 wishes");
		size_t lps = s.find("%1");
		if (lps != std::string::npos) {
			s = s.substr(0, lps) + itemName + s.substr(lps + 2);
		}
		std::string title = s;
		lps = title.find("%d");
		if (lps != std::string::npos) {
			title = title.substr(0, lps) + "'m'" + title.substr(lps + 2);
		}
		lps = title.find("%2");
		if (lps != std::string::npos) {
			title = title.substr(0, lps) + "'n'" + title.substr(lps + 2);
		}
		if (ImPlot::BeginPlot(title.c_str())) {
			ImPlot::SetupAxes(_("Number of Wishes"), _("prob."), ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
			{
				MyDrawHighlight hl;
				for (int i = 1; i < (int)outcomesByCount.size(); i++) {
					const VectorWithOffset<double>& o = outcomesByCount[i];
					int mm = std::min(m - o.offset + 1, (int)o.data.size());
					if (mm > 0) {
						bool draw = false;
						for (int ii = 0; ii < mm; ii++) {
							if (o.data[ii] >= EPS_LARGE) {
								draw = true;
								break;
							}
						}
						if (draw) {
							std::string mStr = std::to_string(i);
							title = s;
							lps = title.find("%d");
							if (lps != std::string::npos) {
								title = title.substr(0, lps) + mStr + title.substr(lps + 2);
							}
							lps = title.find("%2");
							if (lps != std::string::npos) {
								title = title.substr(0, lps) + "%d" + title.substr(lps + 2);
							}
							hl.PlotLine(mStr.c_str(), o.data.data(), EPS_LARGE, 1.0 - EPS_LARGE, mm, MyFormatProb(title + ": %0.4f%%"), o.offset);
						}
					}
				}
			}
			ImPlot::EndPlot();
		}
	}
	void drawNumWishesChart(int m) const {
		drawNumWishesChart(outcomesByCount, m);
	}
	static double getOutcomesByCount(const std::vector<VectorWithOffset<double> >& outcomesByCount, int numItemAtLeast, int numWishes) {
		if (numItemAtLeast <= 0) return 1.0;
		if (numItemAtLeast >= (int)outcomesByCount.size()) return 0.0;
		const VectorWithOffset<double>& o = outcomesByCount[numItemAtLeast];
		if (o.data.empty() || numWishes < o.offset) return 0.0;
		numWishes = std::min(numWishes - o.offset, (int)o.data.size() - 1);
		return o.data[numWishes];
	}
	double getOutcomesByCount(int numItemAtLeast, int numWishes) const {
		return getOutcomesByCount(outcomesByCount, numItemAtLeast, numWishes);
	}
};

enum OutcomeSpecified5StarType {
	PROMOTIONAL_5_STAR,
	PROMOTIONAL_5_STAR_2,
	OTHER_5_STAR,
	COUNT_5_STAR,
};

struct OutcomeSpecified5Star {
	int lastWishType;
	int lastGuaranteeCounter; // 0 or 1
	int lastFatePoint; // 0 = disabled, 1 = 0, 2 = 1, 3 = 2
	int maxFatePoint; // 1 or 2 !!! `not 2 or 3 !!!
	int lastCapturingRadiance; // 0 = disabled, 1 = 0, 2 = 1, 3 = 2, 4 = 3
	std::map<int, double> lastState;
	std::array<std::vector<VectorWithOffset<double> >, COUNT_5_STAR> outcomes;

	OutcomeSpecified5Star() : lastWishType(-1), lastGuaranteeCounter(-1), lastFatePoint(-1), maxFatePoint(1), lastCapturingRadiance(-1) {}
	void calc(int m, int wt = -1, int gc = -1, int fp = -1, int cr = -1) {
		if (wt < 0) wt = lastWishType;
		if (gc < 0) gc = lastGuaranteeCounter;
		if (fp < 0) fp = lastFatePoint;
		if (cr < 0) cr = lastCapturingRadiance;

		wt = std::min(std::max(wt, 0), IM_ARRAYSIZE(wishTypeText) - 1);
		gc = std::min(std::max(gc, 0), 1);
		fp = std::min(std::max(fp, 0), 3);
		cr = std::min(std::max(cr, 0), 4);

		if (wt != lastWishType || gc != lastGuaranteeCounter || fp != lastFatePoint || cr != lastCapturingRadiance) {
			lastWishType = wt;
			lastGuaranteeCounter = gc;
			lastFatePoint = fp;
			lastCapturingRadiance = cr;

			for (int i = 0; i < COUNT_5_STAR; i++) outcomes[i].clear();

			lastState.clear();
			lastState[encode(gc, fp, cr, 0, 0, 0)] = 1.0;

			VectorWithOffset<double> o;
			o.data.push_back(1.0);
			o.offset = 0;
			for (int i = 0; i < COUNT_5_STAR; i++) outcomes[i].push_back(o);
		}

		while (m + 1 > (int)outcomes[0].size()) {
			std::map<int, double> newState;

			for (auto it = lastState.cbegin(); it != lastState.cend(); ++it) {
				int n1, n2, n3;
				decode(it->first, gc, fp, cr, n1, n2, n3);
				switch (lastWishType) {
				case CHARACTER_EVENT_WISH:
					if (gc > 0) {
						addToState(newState, 0, 0, cr, n1 + 1, n2, n3, it->second);
					}
					else {
						if (cr >= 4) {
							addToState(newState, 0, 0, 2, n1 + 1, n2, n3, it->second);
						}
						else if (cr == 3) {
							addToState(newState, 0, 0, 2, n1 + 1, n2, n3, it->second * CAPTURE_RADIANCE_PROB_2);
							addToState(newState, 1, 0, 4, n1, n2, n3 + 1, it->second * (1.0 - CAPTURE_RADIANCE_PROB_2));
						}
						else {
							addToState(newState, 0, 0, cr > 0 ? std::max(cr - 1, 1) : 0, n1 + 1, n2, n3, it->second * 0.5);
							addToState(newState, 1, 0, cr > 0 ? (cr + 1) : 0, n1, n2, n3 + 1, it->second * 0.5);
						}
					}
					break;
				case WEAPON_EVENT_WISH:
					if (fp > maxFatePoint) {
						addToState(newState, 0, fp > 0 ? 1 : 0, 0, n1 + 1, n2, n3, it->second);
					}
					else if (gc > 0) {
						addToState(newState, 0, fp > 0 ? 1 : 0, 0, n1 + 1, n2, n3, it->second * 0.5);
						addToState(newState, 0, fp > 0 ? (fp + 1) : 0, 0, n1, n2 + 1, n3, it->second * 0.5);
					}
					else {
						addToState(newState, 0, fp > 0 ? 1 : 0, 0, n1 + 1, n2, n3, it->second * 0.375);
						addToState(newState, 0, fp > 0 ? (fp + 1) : 0, 0, n1, n2 + 1, n3, it->second * 0.375);
						addToState(newState, 1, fp > 0 ? (fp + 1) : 0, 0, n1, n2, n3 + 1, it->second * 0.25);
					}
					break;
				case CHRONICLED_WISH:
					if (fp > maxFatePoint) {
						addToState(newState, 0, fp > 0 ? 1 : 0, 0, n1 + 1, n2, n3, it->second);
					}
					else {
						addToState(newState, 0, fp > 0 ? 1 : 0, 0, n1 + 1, n2, n3, it->second * 0.5);
						addToState(newState, 0, fp > 0 ? (fp + 1) : 0, 0, n1, n2, n3 + 1, it->second * 0.5);
					}
					break;
				default:
					addToState(newState, 0, 0, 0, n1 + 1, n2, n3, it->second);
					break;
				}
			}
			lastState = newState;

			for (int i = 0; i < COUNT_5_STAR; i++) addOutcome(newState, i);
		}
	}
	// TEST
	void drawTest() {
		for (int i = 0; i < COUNT_5_STAR; i++) {
			std::string iStr = std::to_string(i);
			if (ImGui::TreeNode(iStr.c_str())) {
				if (ImPlot::BeginPlot("Prob.")) {
					calc(10);
					ImPlot::SetupAxes("num.", "prob.", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
					{
						MyDrawHighlight hl;
						for (int j = 1; j <= 10; j++) {
							const VectorWithOffset<double>& o = outcomes[i][j];
							std::string mStr = std::to_string(j);
							hl.PlotLine(mStr.c_str(), o.data.data(), EPS_LARGE, (int)o.data.size(),
								MyFormatProb("prob. of %d after " + mStr + " 5-star: %0.4f%%"), o.offset);
						}
					}
					ImPlot::EndPlot();
				}
				ImGui::TreePop();
			}
		}
	}
private:
	void addToState(std::map<int, double>& state, int gc, int fp, int cr, int n1, int n2, int n3, double p) const {
		if (p < EPS) return;
		int st = encode(gc, fp, cr, n1, n2, n3);
		if (state.find(st) == state.end()) {
			state[st] = p;
		}
		else {
			state[st] += p;
		}
	}
	void addOutcome(const std::map<int, double>& state, int index) {
		VectorWithOffset<double> newOutcome;
		newOutcome.offset = 0;
		for (auto it = state.begin(); it != state.end(); ++it) {
			newOutcome.add((it->first >> (index * 8 + 4)) & 0xFF, it->second);
		}
		newOutcome.shrink(EPS);
		outcomes[index].push_back(newOutcome);
	}
	int encode(int gc, int fp, int cr, int n1, int n2, int n3) const {
		n1 = ((n1 & 0xFF) << 4) | ((n2 & 0xFF) << 12) | ((n3 & 0xFF) << 20);
		switch (lastWishType) {
		case CHARACTER_EVENT_WISH:
			n1 |= (gc & 0x1) | ((cr & 0x7) << 1);
			break;
		case WEAPON_EVENT_WISH:
			n1 |= (gc & 0x1) | ((fp & 0x3) << 1);
			break;
		case CHRONICLED_WISH:
			n1 |= ((fp & 0x3) << 1);
			break;
		}
		return n1;
	}
	void decode(int n, int& gc, int& fp, int& cr, int& n1, int& n2, int& n3) const {
		gc = fp = cr = 0;
		n1 = (n >> 4) & 0xFF;
		n2 = (n >> 12) & 0xFF;
		n3 = (n >> 20) & 0xFF;
		switch (lastWishType) {
		case CHARACTER_EVENT_WISH:
			gc = n & 0x1;
			cr = (n >> 1) & 0x7;
			break;
		case WEAPON_EVENT_WISH:
			gc = n & 0x1;
			fp = (n >> 1) & 0x3;
			break;
		case CHRONICLED_WISH:
			fp = (n >> 1) & 0x3;
			break;
		}
	}
};

struct CalculatorData {
	int wishType, pityCounter, guaranteeCounter;
	int fatePoint; // 0 = disabled, 1 = 0, 2 = 1, 3 = 2
	int capturingRadiance; // 0 = disabled, 1 = 0, 2 = 1, 3 = 2, 4 = 3
	int numWishes;
	int goal;

	CalculatorData() : wishType(0), pityCounter(0), guaranteeCounter(0), fatePoint(0), capturingRadiance(0), numWishes(0), goal(0) {}

	void clamp() {
		wishType = std::min(std::max(wishType, 0), IM_ARRAYSIZE(wishTypeText) - 1);
		pityCounter = std::min(std::max(pityCounter, 0), 100);
		guaranteeCounter = std::min(std::max(guaranteeCounter, 0), 1);
		fatePoint = std::min(std::max(fatePoint, 0), 3);
		capturingRadiance = std::min(std::max(capturingRadiance, 0), 4);
		numWishes = std::min(std::max(numWishes, 0), 2000);
		goal = std::min(std::max(goal, 0), 10);
	}
	void draw(bool readonly, bool showNumWishes, bool showGoal) {
		bool showGuarantee = wishType == CHARACTER_EVENT_WISH || wishType == WEAPON_EVENT_WISH;
		bool showFatePoint = wishType == WEAPON_EVENT_WISH || wishType == CHRONICLED_WISH;
		bool showRadiance = wishType == CHARACTER_EVENT_WISH;

		clamp();

		if (readonly) {
			ImGui::Text("%s: %s", _("Wish Type"), _(wishTypeText[wishType]));
			ImGui::Text("%s: %d", _("Pity Counter"), pityCounter);
		}
		else {
			std::string translated = _S(wishTypeText[0]) + '\0' + _S(wishTypeText[1]) + '\0' + _S(wishTypeText[2]) + '\0' + _S(wishTypeText[3]) + '\0' + '\0';
			ImGui::Combo(_IG("Wish Type"), &wishType, translated.c_str());
			ImGui::InputInt(_IG("Pity Counter"), &pityCounter, 1, 10);
		}
		ImGui::SameLine();
		MyHelpMarker(_("Number of wishes since last 5-star item"));
		if (showGuarantee) {
			if (readonly) {
				ImGui::Text("%s: %d", _("Guarantee"), guaranteeCounter);
			}
			else {
				ImGui::InputInt(_IG("Guarantee"), &guaranteeCounter, 1, 1);
			}
			ImGui::SameLine();
			MyHelpMarker(_("0 if last 5-star item is promotional, otherwise 1"));
		}
		const char* capturingRadianceText[] = { "Disabled", "0", "1", "2", "3" };
		if (showFatePoint) {
			if (readonly) {
				ImGui::Text("%s: %s", _("Fate Point"), _(capturingRadianceText[fatePoint]));
			}
			else {
				std::string translated = _S(capturingRadianceText[0]) + '\0' + '0' + '\0' + '1' + '\0' + '2' + '\0' + '\0';
				ImGui::Combo(_IG("Fate Point"), &fatePoint, translated.c_str());
			}
			ImGui::SameLine();
			if (wishType == WEAPON_EVENT_WISH) {
				MyHelpMarker(_("Maximal fate point is 1 (was 2 before version 5.0)"));
			}
			else {
				MyHelpMarker(_("Maximal fate point is 1"));
			}
		}
		if (showRadiance) {
			if (readonly) {
				ImGui::Text("%s: %s", _("Capturing Radiance"), _(capturingRadianceText[capturingRadiance]));
			}
			else {
				std::string translated = _S(capturingRadianceText[0]) + '\0' + '0' + '\0' + '1' + '\0' + '2' + '\0' + '3' + '\0' + '\0';
				ImGui::Combo(_IG("Capturing Radiance"), &capturingRadiance, translated.c_str());
			}
			ImGui::SameLine();
			MyHelpMarker(_("Counter added at version 5.0\n"
				"initial 1, +1 if lose 50/50, -1 if win 50/50\n"
				"if it's 2 or 3 then winning probability of 50/50 will be raised"));
		}
		if (showNumWishes) {
			if (readonly) {
				ImGui::Text("%s: %d", _("Number of Wishes"), numWishes);
			}
			else {
				ImGui::InputInt(_IG("Number of Wishes"), &numWishes, 1, 10);
			}
			ImGui::SameLine();
			MyHelpMarker(_("Number of wishes you have"));
		}
		if (showGoal) {
			if (readonly) {
				ImGui::Text("%s: %d", _("Goal"), goal);
			}
			else {
				ImGui::InputInt(_IG("Goal"), &goal, 1, 10);
			}
			ImGui::SameLine();
			MyHelpMarker(_("Number of specified 5-star items you want"));
		}

		clamp();
	}
	void drawGoalProb(double prob) const {
		std::string tmp = _S("Prob. of get at least %1 specified 5-star in %2 wishes");
		size_t lps = tmp.find("%1");
		if (lps != std::string::npos) {
			tmp = tmp.substr(0, lps) + std::to_string(goal) + tmp.substr(lps + 2);
		}
		lps = tmp.find("%2");
		if (lps != std::string::npos) {
			tmp = tmp.substr(0, lps) + std::to_string(numWishes) + tmp.substr(lps + 2);
		}
		ImGui::Text("%s: %0.4f%%", tmp.c_str(), prob * 100.0);
	}
};

struct OutcomeSpecified5Star2 {
	CalculatorData lastData;
	std::array<std::vector<VectorWithOffset<double> >, COUNT_5_STAR> outcomes, outcomesByCount;

	OutcomeSpecified5Star2() {}
	void calc(Outcome5Star& o1, OutcomeSpecified5Star& o2, const CalculatorData& data) {
		o1.calc(data.numWishes, data.wishType, data.pityCounter);
		o2.calc(o1.max5Star, data.wishType, data.guaranteeCounter, data.fatePoint, data.capturingRadiance);

		if (data.wishType != lastData.wishType ||
			data.pityCounter != lastData.pityCounter ||
			data.guaranteeCounter != lastData.guaranteeCounter ||
			data.fatePoint != lastData.fatePoint ||
			data.capturingRadiance != lastData.capturingRadiance)
		{
			lastData = data;

			VectorWithOffset<double> o;
			o.data.push_back(1.0);
			o.offset = 0;
			for (int t = 0; t < COUNT_5_STAR; t++) {
				outcomes[t].clear();
				outcomesByCount[t].clear();
				outcomes[t].push_back(o);
				outcomesByCount[t].push_back(o);
			}
		}

		while (data.numWishes + 1 > (int)outcomes[0].size()) {
			int k = (int)outcomes[0].size();
			const auto& st1 = o1.outcomes[k];

			for (int t = 0; t < COUNT_5_STAR; t++) {
				VectorWithOffset<double> newOutcome;
				for (int i = 0; i < (int)st1.data.size(); i++) {
					const auto& st2 = o2.outcomes[t][st1.offset + i];
					for (int j = 0; j < (int)st2.data.size(); j++) {
						newOutcome.add(st2.offset + j, st1.data[i] * st2.data[j]);
					}
				}
				newOutcome.shrink(EPS);
				outcomes[t].push_back(newOutcome);
				newOutcome.cumsum(true);
				newOutcome.toOutcomesByCount(k, outcomesByCount[t]);
			}
		}
	}
	// TEST
	void drawOutcomeChart(int m) const {
		for (int i = 0; i < COUNT_5_STAR; i++) {
			std::string iStr = std::to_string(i);
			if (ImGui::TreeNode(iStr.c_str())) {
				Outcome5Star::drawOutcomeChart(outcomes[i], m);
				ImGui::TreePop();
			}
		}
	}
	// TEST
	void drawNumWishesChart(int m) const {
		for (int i = 0; i < COUNT_5_STAR; i++) {
			std::string iStr = std::to_string(i);
			if (ImGui::TreeNode(iStr.c_str())) {
				Outcome5Star::drawNumWishesChart(outcomesByCount[i], m);
				ImGui::TreePop();
			}
		}
	}
};

class Calculator {
public:
	CalculatorData data;
	Next5StarProb next5StarProb;
	Outcome5Star outcome5Star;
	OutcomeSpecified5Star spec;
	OutcomeSpecified5Star2 spec2;

	void drawResults() {
		if (ImGui::TreeNode("Prob. of next 5-star item")) {
			if (ImGui::TreeNode("Chart")) {
				next5StarProb.calc(data.wishType, data.pityCounter);
				next5StarProb.drawChart();
				ImGui::TreePop();
			}
			if (ImGui::TreeNode("Table")) {
				next5StarProb.calc(data.wishType, data.pityCounter);
				next5StarProb.drawTable();
				ImGui::TreePop();
			}
			if (ImGui::TreeNode("Percentile")) {
				next5StarProb.calc(data.wishType, data.pityCounter);
				next5StarProb.drawPercentile();
				ImGui::TreePop();
			}
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("Num. of 5-star item")) {
			if (ImGui::TreeNode("Outcome chart for 1 and 10")) {
				outcome5Star.calc(10, data.wishType, data.pityCounter);
				outcome5Star.drawOutcomeChart10();
				ImGui::TreePop();
			}
			if (ImGui::TreeNode("Outcome chart")) {
				outcome5Star.calc(data.numWishes, data.wishType, data.pityCounter);
				outcome5Star.drawOutcomeChart(data.numWishes);
				ImGui::TreePop();
			}
			if (ImGui::TreeNode("Num. of wishes chart")) {
				outcome5Star.calc(data.numWishes, data.wishType, data.pityCounter);
				outcome5Star.drawNumWishesChart(data.numWishes);
				ImGui::TreePop();
			}
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("Outcome chart of specified items")) {
			spec2.calc(outcome5Star, spec, data);
			spec2.drawOutcomeChart(data.numWishes);
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("Num. of wishes chart of specified items")) {
			spec2.calc(outcome5Star, spec, data);
			spec2.drawNumWishesChart(data.numWishes);
			ImGui::TreePop();
		}
	}
};

struct CalculatorHistory {
	int number; // number of this wish, starting from 1
	CalculatorData data; // state after this wish
	int outcome; // outcome of this wish
	double prob; // probability of accompolishing goal

	void draw() const {
		std::string outcomeText;
		ImVec4 clr;

		switch (outcome) {
		case PROMOTIONAL_5_STAR:
			clr = ImVec4(0.8f, 0.8f, 0.0f, 1.0f);
			outcomeText = _("Specified 5-star");
			break;
		case PROMOTIONAL_5_STAR_2:
			clr = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
			outcomeText = _("Other promotional 5-star");
			break;
		case OTHER_5_STAR:
			clr = ImVec4(1.0f, 0.0f, 0.0f, 1.0f);
			outcomeText = _("Other 5-star");
			break;
		default:
			clr = ImVec4(0.8f, 0.8f, 0.8f, 1.0f);
			outcomeText = _S("Not 5-star");
			break;
		}

		ImGui::TextColored(clr, "%d", number);

		if (ImGui::BeginItemTooltip()) {
			ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
			ImGui::Text(_("Wish %d: %s"), number, outcomeText.c_str());
			CalculatorData tmp = data;
			tmp.draw(true, true, true);
			tmp.drawGoalProb(prob);
			ImGui::PopTextWrapPos();
			ImGui::EndTooltip();
		}
	}
};

class CalculatorRealtime {
public:
	CalculatorData data;
	Outcome5Star outcome5Star;
	OutcomeSpecified5Star spec;
	OutcomeSpecified5Star2 spec2;

	std::vector<CalculatorHistory> history;

	std::array<int, 11> stepStatus;

	CalculatorRealtime() {
		stepStatus.fill(COUNT_5_STAR);
	}

	void draw() {
		{
			int last = data.wishType;
			data.draw(false, true, true);
			if (last != data.wishType) {
				stepStatus.fill(COUNT_5_STAR);
			}
		}

		if (ImGui::BeginTabBar("TabBar", ImGuiTabBarFlags_TabListPopupButton | ImGuiTabBarFlags_FittingPolicyScroll)) {
			if (ImGui::BeginTabItem(_IG("General"))) {
				ImGui::Text("%s: %d", _("Number of Wishes"), std::max((int)history.size() - 1, 0));

				outcome5Star.calc(10, data.wishType, data.pityCounter);

				ImGui::Text("%s: %0.4f%%", _("Prob. of next wish is 5-star"), outcome5Star.getOutcomesByCount(1, 1) * 100.0);
				ImGui::Text("%s: %0.4f%%", _("Prob. of next 10 wishes have 5-star"), outcome5Star.getOutcomesByCount(1, 10) * 100.0);

				spec2.calc(outcome5Star, spec, data);

				data.drawGoalProb(Outcome5Star::getOutcomesByCount(spec2.outcomesByCount[0], data.goal, data.numWishes));

				drawRecentWishes();

				drawStepControl();

				ImGui::EndTabItem();
			}
			if (ImGui::BeginTabItem(_IG("History"))) {
				drawHistory();
				ImGui::EndTabItem();
			}
			if (ImGui::BeginTabItem(_IG("History of 5-stars"))) {
				drawHistoryBarChart();
				ImGui::EndTabItem();
			}
			if (ImGui::BeginTabItem(_IG("Dist. of specified 5-star"))) {
				spec2.calc(outcome5Star, spec, data);

				Outcome5Star::drawOutcomeChart(spec2.outcomes[0], data.numWishes, _("specified 5-star"));

				ImGui::EndTabItem();
			}
			if (ImGui::BeginTabItem(_IG("Num wishes chart"))) {
				spec2.calc(outcome5Star, spec, data);

				Outcome5Star::drawNumWishesChart(spec2.outcomesByCount[0], data.numWishes, _("specified 5-star"));

				ImGui::EndTabItem();
			}
			ImGui::EndTabBar();
		}
	}

	void drawHistory(int count = 0) {
		if (ImGui::BeginTable("History", 10)) {
			int end = (int)history.size();
			int begin = count > 0 ? std::max(1, end - count) : 1;
			for (int i = begin; i < end; i++) {
				ImGui::TableNextColumn();
				history[i].draw();
			}
			ImGui::EndTable();
		}
	}

	void drawHistoryBar(const std::pair<int, int>& bar) {
		float w = std::min((float)(bar.second - bar.first) / (data.wishType == WEAPON_EVENT_WISH ? 80.0f : 90.0f), 1.0f);
		float h = (1.0f - w) / 3.0f;

		ImGui::TableNextColumn();
		ImGui::Text("%d", bar.first);
		
		ImGui::TableNextColumn();

		std::string s = std::to_string(bar.second - bar.first) + "###" + std::to_string(bar.first) + "-" + std::to_string(bar.second);
		ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, 0.25f, 0.8f));
		ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, 0.25f, 0.7f));
		ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, 0.25f, 0.6f));
		ImGui::Button(s.c_str(), ImVec2((ImGui::GetContentRegionAvail().x - ImGui::GetFontSize() * 2.0f) * w, 0));
		ImGui::PopStyleColor(3);
		ImGui::SameLine();

		history[bar.second].draw();
	}

	void drawHistoryBarChart(int count = 0) {
		if (history.size() <= 1) return;

		std::vector<std::pair<int, int> > bars;
		int lps = 0;

		for (int i = 1, m = (int)history.size(); i < m; i++) {
			if (i == m - 1 || history[i].outcome == PROMOTIONAL_5_STAR || history[i].outcome == PROMOTIONAL_5_STAR_2 || history[i].outcome == OTHER_5_STAR) {
				bars.push_back(std::pair<int, int>(lps, i));
				lps = i;
			}
		}

		int end = (int)bars.size();
		int begin = count > 0 ? std::max(0, end - count) : 0;

		if (ImGui::BeginTable("HistoryBarChart", 2)) {
			ImGui::TableSetupColumn("1", ImGuiTableColumnFlags_WidthFixed, ImGui::GetFontSize() * 2.0f);
			ImGui::TableSetupColumn("2", ImGuiTableColumnFlags_WidthStretch);
			for (int i = begin; i < end; i++) {
				drawHistoryBar(bars[i]);
			}
			ImGui::EndTable();
		}
	}

	void drawRecentWishes() {
		if (ImGui::TreeNode("Recent wishes", _("Recent %d wishes"), 20)) {
			drawHistory(20);
			ImGui::TreePop();
		}
		if (ImGui::TreeNode("Recent 5-stars", _("Recent %d 5-stars"), 3)) {
			drawHistoryBarChart(3);
			ImGui::TreePop();
		}
	}

	void drawStepButton(int index, float width) {
		std::string text;
		float h = 0.0f, s = 0.0f;

		switch (stepStatus[index]) {
		case PROMOTIONAL_5_STAR:
			h = 1.0f / 6.0f;
			s = 1.0f;
			text = _S("win");
			break;
		case PROMOTIONAL_5_STAR_2:
			h = 0.0f;
			s = 0.25f;
			text = "B";
			break;
		case OTHER_5_STAR:
			h = 0.0f;
			s = 0.25f;
			text = _S("lose");
			break;
		}

		text += "###StepButton";

		ImGui::PushID(index);
		ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, s, 0.8f));
		ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, s, 0.7f));
		ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, s, 0.6f));
		if (ImGui::Button(text.c_str(), ImVec2(width, 0.0f))) {
			ImGui::OpenPopup("StepButtonPopup");
		}
		ImGui::PopStyleColor(3);
		if (ImGui::BeginPopup("StepButtonPopup")) {
			int lastStatus = stepStatus[index];
			bool b;
			b = lastStatus == PROMOTIONAL_5_STAR;
			if (ImGui::MenuItem(_("Specified 5-star"), "", &b)) {
				stepStatus[index] = PROMOTIONAL_5_STAR;
			}
			if (data.wishType == WEAPON_EVENT_WISH) {
				b = lastStatus == PROMOTIONAL_5_STAR_2;
				if (ImGui::MenuItem(_("Other promotional 5-star"), "", &b)) {
					stepStatus[index] = PROMOTIONAL_5_STAR_2;
				}
			}
			if (data.wishType != STANDARD_WISH) {
				b = lastStatus == OTHER_5_STAR;
				if (ImGui::MenuItem(_("Other 5-star"), "", &b)) {
					stepStatus[index] = OTHER_5_STAR;
				}
			}
			b = lastStatus != PROMOTIONAL_5_STAR && lastStatus != PROMOTIONAL_5_STAR_2 && lastStatus != OTHER_5_STAR;
			if (ImGui::MenuItem(_("Not 5-star"), "", &b)) {
				stepStatus[index] = COUNT_5_STAR;
			}
			ImGui::EndPopup();
		}
		ImGui::PopID();
	}

	void drawStepControl() {
		bool b = true;

		if (ImGui::TreeNode(_IG("Clear history"))) {
			if (ImGui::Button(_IG("Clear history")) && b) {
				revertWishes(0x7FFFFFFF);
				b = false;
			}

			ImGui::TreePop();
		}

		if (ImGui::TreeNode(_IG("1 wish"))) {
			drawStepButton(10, ImGui::GetContentRegionAvail().x * 0.125f);

			if (ImGui::Button(_IG("Submit 1 wish")) && b) {
				submitWishes(10, 1);
				b = false;
			}

			ImGui::SameLine();

			if (ImGui::Button(_IG("Revert 1 wish")) && b) {
				revertWishes(1);
				b = false;
			}

			ImGui::TreePop();
		}

		if (ImGui::TreeNode(_IG("10 wishes"))) {
			if (ImGui::BeginTable("10 wishes table", 10)) {
				for (int i = 0; i < 10; i++) {
					ImGui::TableNextColumn();
					drawStepButton(i, ImGui::GetContentRegionAvail().x);
				}
				ImGui::EndTable();
			}

			if (ImGui::Button(_IG("Submit 10 wishes")) && b) {
				submitWishes(0, 10);
				b = false;
			}

			ImGui::SameLine();

			if (ImGui::Button(_IG("Revert 10 wishes")) && b) {
				revertWishes(10);
				b = false;
			}

			ImGui::TreePop();
		}
	}

	void submitWishes(int begin, int count) {
		for (int i = 0; i < count; i++) {
			submitWish(stepStatus[begin + i]);
		}
		stepStatus.fill(COUNT_5_STAR);
	}

	void revertWishes(int count) {
		if (history.empty()) return;

		int m = std::max((int)history.size() - count, 1);

		history.resize(m);
		data = history.back().data;

		if (history.size() <= 1) history.clear();
	}

	void submitWish(int outcome) {
		if (history.empty()) {
			spec2.calc(outcome5Star, spec, data);

			CalculatorHistory newHist;

			newHist.number = 0;
			newHist.data = data;
			newHist.outcome = COUNT_5_STAR;
			newHist.prob = Outcome5Star::getOutcomesByCount(spec2.outcomesByCount[0], data.goal, data.numWishes);

			history.push_back(newHist);
		}

		if (data.numWishes > 0) data.numWishes--;

		switch (outcome) {
		case PROMOTIONAL_5_STAR:
			if (data.wishType == CHARACTER_EVENT_WISH && data.guaranteeCounter == 0 && data.capturingRadiance > 0) {
				if (data.capturingRadiance <= 2) data.capturingRadiance = 1;
				else data.capturingRadiance = 2;
			}
			data.pityCounter = 0;
			data.guaranteeCounter = 0;
			data.fatePoint = data.fatePoint > 0 ? 1 : 0;
			if (data.goal > 0) data.goal--;
			break;
		case PROMOTIONAL_5_STAR_2:
		case OTHER_5_STAR:
			if (data.wishType == CHARACTER_EVENT_WISH && data.capturingRadiance > 0) {
				if (data.capturingRadiance < 4) data.capturingRadiance++;
			}
			data.pityCounter = 0;
			data.guaranteeCounter = (outcome == OTHER_5_STAR) ? 1 : 0;
			data.fatePoint = data.fatePoint > 0 ? (data.fatePoint + 1) : 0;
			break;
		default:
			data.pityCounter++;
			break;
		}

		spec2.calc(outcome5Star, spec, data);

		CalculatorHistory newHist;

		newHist.number = (int)history.size();
		newHist.data = data;
		newHist.outcome = outcome;
		newHist.prob = Outcome5Star::getOutcomesByCount(spec2.outcomesByCount[0], data.goal, data.numWishes);

		history.push_back(newHist);
	}
};

class MainApp : public ImGuiApp {
private:
	ImVector<ImWchar> glyphRanges;
	bool show_demo_window;
	//Calculator calc; // unused
	CalculatorRealtime cr;
public:
	MainApp() {
		windowTitle = L"Genshin Impact Wish Probability Calculator";
		frameDelay = 20;
		show_demo_window = false;

		// load locale
		if (!gt.LoadFileWithAutoLocale("./locale/*.mo")) {
			if (!gt.LoadFileWithAutoLocale("../locale/*.mo")) {
				gt.LoadFileWithAutoLocale("../../locale/*.mo");
			}
		}
	}
	virtual ~MainApp() {
	}
	virtual void setupContext() {
		// Setup Dear ImGui context
		IMGUI_CHECKVERSION();
		ImGui::CreateContext();
		ImPlot::CreateContext();
		ImGuiIO& io = ImGui::GetIO();
		io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
		io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls

		// Setup Dear ImGui style
		//ImGui::StyleColorsDark();
		ImGui::StyleColorsLight();
	}
	virtual void destroyContext() {
		ImPlot::DestroyContext();
		ImGui::DestroyContext();
	}
	virtual void loadFonts() {
		ImGuiIO& io = ImGui::GetIO();
		std::vector<std::string> fileNames;
		fileNames.push_back("./fonts/HYWenHei-85W.ttf");
		fileNames.push_back("../fonts/HYWenHei-85W.ttf");
		fileNames.push_back("../../fonts/HYWenHei-85W.ttf");
		for (const std::string& fileName : fileNames) {
			FILE *f = fopen(fileName.c_str(), "rb");
			if (f != NULL) {
				fclose(f);

				// get used characters
				ImFontGlyphRangesBuilder builder;
				builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
				for (auto it = gt.m_objString.cbegin(); it != gt.m_objString.cend(); ++it) {
					builder.AddText(it->first.c_str());
					builder.AddText(it->second.c_str());
				}
				builder.BuildRanges(&glyphRanges);

				// add fonts
				io.Fonts->AddFontFromFileTTF(fileName.c_str(), 18.0f, NULL, glyphRanges.Data);

				break;
			}
		}
	}
	virtual void drawFrame() {
		ImGuiIO& io = ImGui::GetIO();

		// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
		if (show_demo_window) {
			ImGui::ShowDemoWindow(&show_demo_window);
			ImPlot::ShowDemoWindow(&show_demo_window);
		}

		{
			const ImGuiWindowFlags full_screen_flags = ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings;

			const ImGuiViewport* viewport = ImGui::GetMainViewport();
			ImGui::SetNextWindowPos(viewport->Pos);
			ImGui::SetNextWindowSize(viewport->Size);

			ImGui::Begin(_IG("Home"), NULL, full_screen_flags);

			ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state
			// ImGui::Separator();

			cr.draw();

			ImGui::End();
		}

#if 0
		// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
		{
			ImGui::Begin("Home");                          // Create a window called "Hello, world!" and append into it.

			ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our window open/close state

			ImGui::Separator();

			calc.data.draw(false, true, false);

			ImGui::Separator();

			calc.drawResults();

			ImGui::End();
		}
#endif
	}
};

ImGuiApp* ImGuiApp::create() {
	return new MainApp;
}
